package com.example.blog.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.blog.common.aop.LogAnnotation;
import com.example.blog.dao.mapper.ArticleBodyMapper;
import com.example.blog.dao.mapper.ArticleMapper;
import com.example.blog.dao.mapper.ArticleTagMapper;
import com.example.blog.dos.Archives;
import com.example.blog.entity.*;
import com.example.blog.service.*;
import com.example.blog.utils.UserThreadLocal;
import com.example.blog.vo.*;
import com.example.blog.vo.params.ArticleBodyParam;
import com.example.blog.vo.params.ArticleParam;
import com.example.blog.entity.ArticleTag;
import com.example.blog.vo.params.PageParams;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class ArticleServiceImpl implements ArticleService {
    @Autowired
    private ArticleMapper articleMapper;

    @Autowired
    private TagService tagService;

    @Autowired
    private SysUserService sysUserService;

    @Autowired
    private ArticleBodyMapper articleBodyMapper;

    @Autowired
    private CategoryService categoryService;

    @Autowired
    private ArticleTagMapper articleTagMapper;

    /**
     * 分页查询 article数据库表
     *
     * @param pageParams
     * @return
     */
    //加上此注解 代表要对此接口记录日志
    @LogAnnotation(module = "文章", operator = "获取文章列表")
    @Override
    public Result listArticle(PageParams pageParams) {
        Page<Article> page = new Page<>(pageParams.getPage(), pageParams.getPageSize());
        IPage<Article> articleIPage = articleMapper.listArticle(
                page,
                pageParams.getCategoryId(),
                pageParams.getTagId(),
                pageParams.getYear(),
                pageParams.getMonth());
        List<Article> records = articleIPage.getRecords();
        return Result.success(copyList(records, true, true));
    }


//    /**
//     * 分页查询 article数据库表
//     * @param pageParams
//     * @return
//     */
//    @Override
//    //加上此注解 代表要对此接口记录日志
//    @LogAnnotation(module="文章",operator="获取文章列表")
//    public Result listArticle(PageParams pageParams)
//    {
//        Page<Article> page = new Page<>(pageParams.getPage(), pageParams.getPageSize());
//        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();/*查询器*/
//        queryWrapper.orderByDesc(Article::getWeight);/*是否置顶进行排序*/
//        queryWrapper.orderByDesc(Article::getCreateDate);/*根据创建时间进行降序排序 order by create_date desc*/
//
//        if(null != pageParams.getCategoryId())//如果ageParams.getCategoryId()不为空，说明是查找的某个文章分类下的文章列表，如果为空，说明查的是总文章列表
//        {
//            queryWrapper.eq(Article::getCategoryId,pageParams.getCategoryId());
//        }
//        //这里的article并没有tagId这个字段，而是通过ms_article_tag来查找的
//        //所以需要通过ms_article_tag表查到对应的articleId列表，再通过articleId列表查找article列表
//        if(null != pageParams.getTagId())
//        {
//            List<Long> articleIdList = new ArrayList<>();
//            LambdaQueryWrapper<ArticleTag> ATQueryWrapper = new LambdaQueryWrapper<>();
//            ATQueryWrapper.eq(ArticleTag::getTagId,pageParams.getTagId());
//            List<ArticleTag> articleTags = articleTagMapper.selectList(ATQueryWrapper);
//            for(ArticleTag articleTag: articleTags)
//            {
//                articleIdList.add(articleTag.getArticleId());
//            }
//            if(articleIdList.size() > 0)
//            {
//                queryWrapper.in(Article::getId,articleIdList);
//            }
//        }
//        Page<Article> articlePage = articleMapper.selectPage(page, queryWrapper);/*等同于编写一个普通list查询，mybatis-plus自动替你分页*/
//        List<Article> records = articlePage.getRecords();
//        List<ArticleVo> articleVoList = copyList(records,true,true);
//        return Result.success(articleVoList);
//    }

    /**
     * 将Article列表封装为ArticleVo列表，以便把数据传给前端
     *
     * @param records
     * @param isTag
     * @param isAuthor
     * @return
     */
    private List<ArticleVo> copyList(List<Article> records, boolean isTag, boolean isAuthor) {
        ArrayList<ArticleVo> articleVoList = new ArrayList<>();
        for (Article record : records) {
            articleVoList.add(copy(record, isTag, isAuthor, false, false));
        }
        return articleVoList;
    }


    /*并不是所有接口都需要标签和作者信息*/
    private ArticleVo copy(Article article, boolean isTag, boolean isAuthor, boolean isBody, boolean isCategory) {
        ArticleVo articleVo = new ArticleVo();

        articleVo.setId(String.valueOf(article.getId()));

        BeanUtils.copyProperties(article, articleVo);/*可以理解为article相同成员赋值给articleVo*/
        /*BeanUtils.copyProperties(article, articleVo);
        articleVo中的存在的属性，article中一定要有，但是article中可以有多余的属性；
        article中与articleVo中相同的属性都会被替换，不管是否有值；
        article、articleVo中的属性要名字相同，才能被赋值，不然的话需要手动赋值；
        Spring的BeanUtils的CopyProperties方法需要对 应的属性有getter和setter方法；
        如果存在属性完全相同的内部类，但是不是同一个内部类，即分别属于各自的内部类，则spring会认为属性不同，不会copy*/
        articleVo.setCreateDate(new DateTime(article.getCreateDate()).toString("yyyy-MM-dd HH:mm"));
        /*因为Article中的createDate(Long)与ArticleVo(String)中的createDate类型不同，所以需要进行手动赋值*/
        if (isTag) {
            Long articleId = article.getId();
            articleVo.setTags(tagService.findTagsById(articleId));
        }
        if (isAuthor) {
            /*为什么需要传作者id，因为每篇文章的详情界面需要判断是不是该作者创建的，如果是的话，就需要显示编辑按钮*/
            Long authorId = article.getAuthorId();
            articleVo.setAuthorId(String.valueOf(authorId));
            /*这个是传的作者名*/
            articleVo.setAuthor(sysUserService.findUserById(authorId).getNickname());
            articleVo.setAvatar(sysUserService.findUserById(authorId).getAvatar());
        }
        /*查询文章内容*/
        if (isBody) {
            Long bodyId = article.getBodyId();
            articleVo.setBody(findArticleBodyById(bodyId));
        }
        if (isCategory) {
            Long categoryId = article.getCategoryId();
            articleVo.setCategory(categoryService.findCategoryById(categoryId));
        }
        return articleVo;
    }

    /**
     * 通过文章id 查询文章详情表中的文章详情
     *
     * @param bodyId
     * @return
     */
    private ArticleBodyVo findArticleBodyById(Long bodyId) {
        ArticleBody articleBody = articleBodyMapper.selectById(bodyId);
        ArticleBodyVo articleBodyVo = new ArticleBodyVo();
        articleBodyVo.setContent(articleBody.getContent());
        return articleBodyVo;
    }

    @Override
    public Result hotArticle(int limit) {
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.select(Article::getId, Article::getTitle);//只取id和title这两条数据
        queryWrapper.orderByDesc(Article::getViewCounts);
        queryWrapper.last("limit " + limit);//最后加上条数限制
        /*select id,title from article order by view_counts desc limit 5*/
        List<Article> articleList = articleMapper.selectList(queryWrapper);
        /*mybatisPlus有自己的默认对应规则，例如实体类名为：OwnUser，会给你对应到数据库中的own_user表。这是MP源代码中实现的实体与表的对应关系，
        如果不想用默认的对应规则，可以在类中使用@TableName()注解，进行表名指定。*/
        List<ArticleVo> articleVoList = copyList(articleList, false, false);
        return Result.success(articleVoList);
    }

    @Override
    public Result newArticle(int limit) {
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        /*select id,title from article order by createDate limit 5*/
        queryWrapper.select(Article::getId, Article::getTitle);
        queryWrapper.orderByDesc(Article::getCreateDate);
        queryWrapper.last("limit " + limit);

        List<Article> articleList = articleMapper.selectList(queryWrapper);
        List<ArticleVo> articleVos = copyList(articleList, false, false);
        return Result.success(articleVos);
    }

    /**
     * 文章归档
     *
     * @return
     */
    @Override
    public Result listArchives() {
        /**
         * 对每年每月的文章进行分组，并统计它们的数量
         */
        /* select year(create_date) as year,month(create_date) as month,count(*) as count from ms_article
        group by year,month*/
        List<Archives> archivesList = articleMapper.listArchives();
        return Result.success(archivesList);
    }

    /**
     * 文章详情
     *
     * @param articleId
     * @return
     */
    @Autowired
    private ThreadService threadService;

    @Override
    public Result findArticleById(Long articleId) {
        /**
         * 1.把ArticleVo对应的ArticleBodyVo 和categories注释去掉，也就是现在需要使用这两个成员，并添加对应的Vo类
         * 2.根据articleId查询文章article
         * 3.根据bodyId 和categoryId 去关联查询对应的文章详情和文章标签
         * 4.通过article中的authorId查询ms_sys_user表中的avatar
         * 5.将查到的文章详情和文章标签注入到articleVo中
         * 6.
         */
        Article article = articleMapper.selectById(articleId);
        ArticleVo articleVo = copy(article, true, true, true, true);
        //查看完该文章之后，本应该直接返回数据，这个时候做了一个更新操作，更新时加写锁，阻塞其他的读操作，性能就会比较低
        //更新肯定会增加此次接口的耗时，如果更新一旦除了问题，不能影响 查看文章的操作
        //这时可以增加线程池，把更新操作扔到线程池中去执行，这样就和主线程不相关了
        threadService.updateArticleViewCount(articleMapper, article);
        return Result.success(articleVo);
    }

    /**
     * 文章发布
     *
     * @param articleParam
     * @return
     */
    @Override
    public Result publish(ArticleParam articleParam) {
        /**
         * 1.发布文章 目的是构建Article对象,并添加对应属性
         * 2.作者id 从当前的登录用户获取 (该登录接口需要加入到登录拦截器中,不然获得到的id可能为空）
         * 3.标签 要将标签加入到 关联列表中(ms_article_tag),需要先插入文章到数据库中，让数据库生成文章id
         * 4.body 文章内容存储,插入之后还需要把articleBodyId返回给article对象
         *
         */
        Article article = new Article();
        article.setWeight(Article.Article_Common);//是否置顶，设为默认不置顶
        article.setViewCounts(0);
        article.setTitle(articleParam.getTitle());
        article.setSummary(articleParam.getSummary());//摘要
        article.setCommentCounts(0);
        article.setCreateDate(System.currentTimeMillis());
        article.setCategoryId(Long.parseLong(articleParam.getCategory().getId()));

        //2.
        SysUser sysUser = UserThreadLocal.get();
        article.setAuthorId(sysUser.getId());
        //3.
        //文章插入到数据库之后，就会生成一个文章id
        articleMapper.insert(article);
        Long articleId = article.getId();

        List<TagVo> tags = articleParam.getTags();
        if (null != tags) {
            for (TagVo tagVo : tags) {
                ArticleTag articleTag = new ArticleTag();

                articleTag.setTagId(Long.parseLong(tagVo.getId()));

                articleTag.setArticleId(articleId);
                articleTagMapper.insert(articleTag);
            }
        }
        //4.内容存储
        ArticleBody articleBody = new ArticleBody();
        ArticleBodyParam articlebodyParam = articleParam.getBody();
        articleBody.setContent(articlebodyParam.getContent());
        articleBody.setContentHtml(articlebodyParam.getContentHtml());
        articleBody.setArticleId(articleId);
        articleBodyMapper.insert(articleBody);
        article.setBodyId(articleBody.getId());
        //插入articleBodyId之后，需要进行article在数据库中的更新
        articleMapper.updateById(article);

        Map<String, String> map = new HashMap<>();
        //这里也有精度损失的问题，需要在ArticleVo类中的id设置序列化
        map.put("id", articleId.toString());

        return Result.success(map);
    }
}
